package jannex.label;

import jannex.internal.BaseIterator;
import jannex.internal.Hashing;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

class ArrayLabelMap<V> extends LabelMap<V> {

    private final String[] labels;
    private final V[] objects;
    private final String[] keyTable;
    private final V[] objectsTable;

    private transient Set<Entry<String, V>> entrySet;

    ArrayLabelMap(String[] labels, V[] objects, String[] keyTable, V[] objectsTable) {
        this.labels = labels;
        this.objects = objects;
        this.keyTable = keyTable;
        this.objectsTable = objectsTable;
    }

    @Override
    public LabelSet keySet() {
        return new ArrayLabelSet(labels, keyTable);
    }

    @Override
    LabelMap<V> newFromValues(V[] objects) {
        @SuppressWarnings("unchecked")
        V[] objectsTable = (V[]) new Object[keyTable.length];
        for (int i = 0; i < labels.length; i++) {
            String label = labels[i];
            for (int j = Hashing.smear(label.toLowerCase().hashCode()); ; j++) {
                String candidate = keyTable[j & (keyTable.length - 1)];
                if (label.equalsIgnoreCase(candidate)) {
                    objectsTable[j & (keyTable.length - 1)] = objects[i];
                    break;
                }
            }
        }
        return new ArrayLabelMap<>(labels, objects, keyTable, objectsTable);
    }

    @Override
    public Set<Entry<String, V>> entrySet() {
        if (entrySet != null) {
            return entrySet;
        }
        entrySet = new LabelEntrySet<V>() {
            @SuppressWarnings("unchecked")
            private final LabelEntry<V>[] entries = new LabelEntry[objects.length];

            {
                for (int i = 0; i < entries.length; i++) {
                    entries[i] = new LabelEntry<>(labels[i], objects[i]);
                }
            }

            @Override
            public Iterator<Entry<String, V>> iterator() {
                return new BaseIterator<Entry<String, V>>() {
                    int index;

                    @Override
                    public boolean hasNext() {
                        return index < entries.length;
                    }

                    @Override
                    public Entry<String, V> next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException();
                        }
                        return entries[index++];
                    }
                };
            }

            @Override
            public int size() {
                return labels.length;
            }
        };
        return entrySet;
    }

    @Override
    public V get(Object key) {
        if (key == null || !(key instanceof String)) {
            return null;
        }
        String targetString = key.toString();
        for (int i = Hashing.smear(targetString.toLowerCase().hashCode()); ; i++) {
            String candidate = keyTable[i & (keyTable.length - 1)];
            if (candidate == null) {
                return null;
            }
            if (candidate.equalsIgnoreCase(targetString)) {
                return objectsTable[i & (keyTable.length - 1)];
            }
        }
    }

    @Override
    public Collection<V> values() {
        return Arrays.asList(objects);
    }

    @Override
    public boolean containsValue(Object value) {
        if (value == null) {
            for (V object : objects) {
                if (object == null) {
                    return true;
                }
            }
        } else {
            for (V object : objects) {
                if (value.equals(object)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean containsKey(Object key) {
        return get(key) != null;
    }

    @Override
    public int size() {
        return labels.length;
    }
}
